use std::ffi::{CStr, CString};

use libc::c_void;

use crate::{ffi::*, core::{Value, GetterMap, LuaEngine, LuaNumber}};

pub unsafe fn get_index_string(state: *mut lua_State) -> String {
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR |                       SET ACCESS TABLE INDEX_STR
    let index_string = lua_tolstring(state, 2, std::ptr::null_mut());
    let index_string = String::from_utf8(CStr::from_ptr(index_string).to_bytes().to_vec()).unwrap();
    index_string
}

pub unsafe fn get_object_address(state: *mut lua_State) -> i64 {
    let object_address;
    match lua_type(state, 1) {
        2 => {
            object_address = lua_touserdata(state, 1) as usize as i64;
        },
        5 => {
            let c_string = CString::new("Object").expect("");

            lua_pushstring(state, c_string.as_ptr());
            // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | Object |                       SET ACCESS TABLE INDEX_STR
            lua_gettable(state, 1);
            // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | TABLE.Object |                 FIND TABLE.__ObjectAddress
            object_address = lua_tointegerx(state, -1, std::ptr::null_mut());
            // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | TABLE.Object |                 GET VALUE TABLE.__ObjectAddress
            lua_settop(state, -2);
            // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR |                                POP VALUE TABLE.__ObjectAddress
        },
        _ => unreachable!()
    }
    object_address
}

pub unsafe fn get_struct_name(state: *mut lua_State) -> String {
    // Get Metatable
    lua_getmetatable(state, 1);
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | TABLE.METATABLE |                          POP VALUE TABLE.__ObjectAddress

    let c_string = CString::new("__name").expect("");
    lua_pushstring(state, c_string.as_ptr());
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | TABLE.METATABLE | "__name" |               SET ACCESS TABLE __name
    lua_rawget(state, -2);
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | TABLE.METATABLE | METATABLE.__name |       FIND TABLE.__name
    let struct_name = lua_tolstring(state, -1, std::ptr::null_mut());
    let struct_name = String::from_utf8(CStr::from_ptr(struct_name).to_bytes().to_vec()).unwrap();
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | TABLE.METATABLE | TABLE.__name |           GET VALUE TABLE.__name
    lua_settop(state, -3);
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR |                                            POP VALUE TABLE.__name

    struct_name
}

pub unsafe fn get_value(state: *mut lua_State, object_address: i64, struct_name: &String, index_string: &String) -> Value {
    // Get function
    let c_string = CString::new("getter_map").expect("");
    lua_getglobal(state, c_string.as_ptr());
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | getter_map |                               POP VALUE TABLE.__name

    // Get Value
    let getter_map_address = &*(lua_touserdata(state, -1) as usize as *mut libc::c_void as *mut GetterMap);
    let getter = getter_map_address.get(struct_name);

    // Clear
    lua_settop(state, -2);
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR |                               POP VALUE TABLE.__name

    if getter.is_none() {
        return Value::Nil();
    }
    let value = getter.unwrap()(object_address as *const c_void, &index_string);

    value
}

pub unsafe fn push_luavalue(state: *mut lua_State, value: Value) {
    match value {
        Value::Nil() => {
            return
        },
        Value::LightUserdata(light_ud) => {
            lua_pushlightuserdata(state, light_ud);
        },
        Value::Object((address, name)) => {
            lua_createtable(state, 0, 1);
            // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value |                            PUSH A METATBABLE
            let c_string = CString::new("Object").unwrap();
            lua_pushstring(state, c_string.as_ptr());
            // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value | "Object" |                 PUSH A METATBABLE
            lua_pushinteger(state, address as i64);
            // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value | "Object" | Address |       PUSH A METATBABLE
            lua_rawset(state, -3);
            // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value |                            PUSH A METATBABLE
            
            let c_string = CString::new(name).unwrap();
            luaL_newmetatable(state, c_string.as_ptr());
            // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value | meta_table |               PUSH A METATBABLE

                lua_pushcclosure(state, Some(LuaEngine::get_distributor), 0);
                // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value | meta_table | function |    PUSH A METATBABLE
                let c_string = CString::new("__index").unwrap();
                lua_setfield(state, -2, c_string.as_ptr());
                // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value | meta_table |               SET METATBLE.__index = FUNCTION; POP FUNCTION

                lua_pushcclosure(state, Some(LuaEngine::set_distributor), 0);
                // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value | meta_table | function |    PUSH A METATBABLE
                let c_string = CString::new("__newindex").unwrap();
                lua_setfield(state, -2, c_string.as_ptr());
                // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value | meta_table |               SET METATBLE.__index = FUNCTION; POP FUNCTION

            lua_settop(state, -2);
            // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value |                            POP METATABLE
    
            let c_string = CString::new(name).unwrap();
            luaL_setmetatable(state, c_string.as_ptr());
            // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value |                            SET TABLE.METATABLE = MyMetatable
        },
        Value::Number(lua_number) => {
            match lua_number {
                LuaNumber::Float(float) => 
                    lua_pushnumber(state, float),
                LuaNumber::Integer(integer) => {
                    lua_pushinteger(state, integer);
                },
            }
        },
        _ => unreachable!()
    }
}

pub unsafe fn set_newvalue_to_table(state: *mut lua_State) {
    lua_pushvalue(state, -2);
    println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value | INDEX_STR |                           SET TABLE.METATABLE = MyMetatable
    lua_pushvalue(state, -2);
    println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value | INDEX_STR | new value |                           SET TABLE.METATABLE = MyMetatable
    lua_rawset(state, 1);
    println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value |                           SET TABLE.METATABLE = MyMetatable
}